package model.algorithms.nodesMapping;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;

import model.components.networks.network.Network;
import model.components.networks.network.VirtualNetwork;
import model.components.networks.network.PhysicalNetwork;
import model.components.nodes.comparator.AvailableVirtualNodesNumberComparator;
import model.components.nodes.comparator.NodeNumberComparator;
import model.components.nodes.node.Node;
import model.components.nodes.node.PhysicalRouter;
import model.components.nodes.node.PhysicalServer;
import model.components.nodes.node.PhysicalSwitch;
import model.components.nodes.node.VirtualNode;
import model.components.nodes.node.PhysicalNode;

public class Greedy extends NodesMappingAlgorithm
{
	public Greedy( String name )
	{
		super( name );
	}
	
	@Override
	public LinkedHashMap< VirtualNode, PhysicalNode > run( VirtualNetwork virtualNetwork, PhysicalNetwork physicalNetwork )
	{	
		LinkedHashMap< VirtualNode, PhysicalNode > nodesMapping = new LinkedHashMap< VirtualNode, PhysicalNode >();
		
		PhysicalNetwork copyPhysicalNetwork = physicalNetwork.getCopy();
		double physicalNetworkAvailability[] = this.physicalNetworkAvailability( copyPhysicalNetwork );
		double virtualNodesCapacities[][] = virtualNetwork.getNodesCapacities();
		double physicalNodesAvailableCapacities[][] = copyPhysicalNetwork.getPhysicalNodesAvailableCapacities();
		
		String virtualNodesAttributes[][] = this.getNodesAttributes( virtualNetwork );
		String physicalNodesAttributes[][] = this.getNodesAttributes( copyPhysicalNetwork );
		
		for( VirtualNode virtualNode: virtualNetwork.getVirtualNodes() )
		{
			double max = 0;
			PhysicalNode selectedPhysicalNode = null;
			 
			List< PhysicalNode> physicalNodes = null;
			
			if( virtualNode.getType().equals( Node.ROUTER_TYPE ) )
				physicalNodes = this.getRoutersSubstrateAvailability( copyPhysicalNetwork );
			
			else if( virtualNode.getType().equals( Node.SWITCH_TYPE ) )
				physicalNodes = this.getSwitchesSubstrateAvailability( copyPhysicalNetwork );
			
			else if( virtualNode.getType().equals( Node.SERVER_TYPE ) )
				physicalNodes = this.getServersSubstrateAvailability( copyPhysicalNetwork );
						
			for( PhysicalNode PhysicalNode: physicalNodes )
			{
				int checkCapacities = 0;
				int checkAttributes = 0;
				 
				// Check that capacity constraints are satisfied for every functional type in the node.
				for( int i = 0; i < 3; i++ )
					if( physicalNodesAvailableCapacities[ i ][ PhysicalNode.getNumber() ] >= virtualNodesCapacities[ i ][ virtualNode.getNumber() ] )
						checkCapacities++;
				 
				for( int i = 0; i < 3; i++ )
					if( physicalNodesAttributes[ PhysicalNode.getNumber() ][ i ].equals( virtualNodesAttributes[ virtualNode.getNumber() ][ i ] ) )
						checkAttributes++;
				 
				// If capacity constraints are satisfied check if it has the biggest average
				if( checkCapacities == 3 && checkAttributes == 3 )
					if( physicalNetworkAvailability[ PhysicalNode.getNumber() ] > max )
					{
						max = physicalNetworkAvailability[ PhysicalNode.getNumber() ];
						selectedPhysicalNode = PhysicalNode;
					}
			}
			 
			// Update mapping and remove from pool of available substrate resources
			if( selectedPhysicalNode != null )
			{
				nodesMapping.put( virtualNode, selectedPhysicalNode );
				copyPhysicalNetwork.getGraph().removeVertex( selectedPhysicalNode );
			}
			else
				break;
		}
		 
		if( nodesMapping.size() != virtualNetwork.getNodes().size() )
			nodesMapping = null;
		
		return nodesMapping;
	}

	public List< PhysicalNode > getRoutersSubstrateAvailability( PhysicalNetwork physicalNetwork )
	{	
		List< PhysicalNode > routersSubstrateAvailability = new ArrayList< PhysicalNode >();
		
		for( PhysicalNode nodeSubstrate: physicalNetwork.getPhysicalNodes() )
			if( nodeSubstrate instanceof PhysicalRouter )
				routersSubstrateAvailability.add( nodeSubstrate );
		
		Collections.sort( routersSubstrateAvailability, new NodeNumberComparator() );
		Collections.sort( routersSubstrateAvailability, new AvailableVirtualNodesNumberComparator() );
		
		return routersSubstrateAvailability;
	}
	public List< PhysicalNode > getSwitchesSubstrateAvailability( PhysicalNetwork physicalNetwork )
	{
		List< PhysicalNode > switchesSubstrateAvailability = new ArrayList< PhysicalNode >();
		
		for( PhysicalNode nodeSubstrate: physicalNetwork.getPhysicalNodes() )
			if( nodeSubstrate instanceof PhysicalSwitch )
				switchesSubstrateAvailability.add( nodeSubstrate );
		
		Collections.sort( switchesSubstrateAvailability, new NodeNumberComparator() );
		Collections.sort( switchesSubstrateAvailability, new AvailableVirtualNodesNumberComparator() );
		
		return switchesSubstrateAvailability;
	}
	public List< PhysicalNode > getServersSubstrateAvailability( PhysicalNetwork physicalNetwork )
	{
		List< PhysicalNode > serversSubstrateAvailability = new ArrayList< PhysicalNode >();
		
		for( PhysicalNode nodeSubstrate: physicalNetwork.getPhysicalNodes() )
			if( nodeSubstrate instanceof PhysicalServer )
				serversSubstrateAvailability.add( nodeSubstrate );
		
		Collections.sort( serversSubstrateAvailability, new NodeNumberComparator() );
		Collections.sort( serversSubstrateAvailability, new AvailableVirtualNodesNumberComparator() );
		
		return serversSubstrateAvailability;
	}
	
	public double[] physicalNetworkAvailability( PhysicalNetwork physicalNetwork )
	{	
		double availability[] = new double[ physicalNetwork.getPhysicalNodes().size() ];
			
		for( int i = 0; i < physicalNetwork.getPhysicalNodes().size(); i++ )
		{
			PhysicalNode substrateNode = physicalNetwork.getPhysicalNodes().get( i );
			availability[ i ] = substrateNode.getAvailableCpu() + substrateNode.getAvailableMemory();
			
			if( substrateNode instanceof PhysicalServer )
				availability[ i ] += substrateNode.getAvailableDiskSpace();
		}
			
		//apart form node capacity take into consideration link capacity of links leaving the node
		//Multiply the capacities with sum of adjacent substrate links of every substrate node
		double bandwidthsAvailable[][] = physicalNetwork.getPhysicalLinksAvailableBandwidths();
		double bandwidthAdjacent[] = new double[ physicalNetwork.getPhysicalNodes().size() ];
		
		for( int i = 0; i < physicalNetwork.getPhysicalNodes().size(); i++ )
		{
			for( int j = 0; j < physicalNetwork.getPhysicalNodes().size(); j++ )
				if( bandwidthsAvailable[ i ][ j ] != 0 )
					bandwidthAdjacent[ i ] += bandwidthsAvailable[ i ][ j ];
			 
			availability[ i ] *= bandwidthAdjacent[ i ];
		}
		
		return availability;
	}
	
	public String[][] getNodesAttributes( Network network ) 
	{
		String nodesAttributes[][] = new String[ network.getGraph().getVertexCount() ][ 3 ];
		
		for( Node node: network.getGraph().getVertices() )
		{
			nodesAttributes[ node.getNumber() ][ 0 ] = node.getType();
			nodesAttributes[ node.getNumber() ][ 1 ] = node.getOperatingSystem();
			nodesAttributes[ node.getNumber() ][ 2 ] = node.getVirtualEnvironment();
		}
		
		return nodesAttributes;
	}
}
